package com.tianji.learning.service.impl;

import com.tianji.common.autoconfigure.mq.RabbitMqHelper;
import com.tianji.common.constants.MqConstants;
import com.tianji.common.exceptions.BizIllegalException;
import com.tianji.common.utils.BooleanUtils;
import com.tianji.common.utils.CollUtils;
import com.tianji.common.utils.DateUtils;
import com.tianji.common.utils.UserContext;
import com.tianji.learning.constants.RedisConstants;
import com.tianji.learning.domain.po.SignRecord;
import com.tianji.learning.domain.vo.SignResultVO;
import com.tianji.learning.mapper.SignRecordMapper;
import com.tianji.learning.mq.message.SignInMessage;
import com.tianji.learning.service.ISignRecordService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.RequiredArgsConstructor;
import org.springframework.data.redis.connection.BitFieldSubCommands;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import java.time.LocalDate;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

/**
 * <p>
 * 签到记录表 服务实现类
 * </p>
 *
 * @author Aimer
 * @since 2025-11-16
 */
@Service
@RequiredArgsConstructor
public class SignRecordServiceImpl extends ServiceImpl<SignRecordMapper, SignRecord> implements ISignRecordService {

    private final StringRedisTemplate redisTemplate;
    private final RabbitMqHelper rabbitMqHelper;
    @Override
    public SignResultVO addSignRecords() {
        // 1.签到
        // 1.1.获取当前登录用户id
        Long userId = UserContext.getUser();
        // 1.2.获取日期
        LocalDate now = LocalDate.now();
        // 1.3.拼接key
        String key = RedisConstants.SIGN_DATE_SUFFIX_FORMATTER + userId + now.format(DateUtils.SIGN_DATE_SUFFIX_FORMATTER);
        // 1.4.计算offer
        int offset = now.getDayOfMonth() - 1;
        Boolean exists = redisTemplate.opsForValue().setBit(key, offset, true);
        if (BooleanUtils.isTrue(exists)){
            throw new BizIllegalException("不允许重复签到！");
        }
        // 2.计算连续签到天数
        int signDay = countSignDays(key, now.getDayOfMonth());
        // 3.获取签到积分
        int signPoints = 0;
        switch (signDay){
            case 7:
                signPoints = 10;
                break;
            case 14:
                signPoints = 20;
                break;
            case 28:
                signPoints = 40;
                break;
        }
        // 4.发送mq消息
        rabbitMqHelper.send(
                MqConstants.Exchange.LEARNING_EXCHANGE,
                MqConstants.Key.SIGN_IN,
                SignInMessage.of(userId,signPoints+1)
        );
        SignResultVO signResultVO = new SignResultVO();
        signResultVO.setSignDays(signDay);
        signResultVO.setRewardPoints(signPoints);
        return signResultVO;
    }

    @Override
    public List<Byte> getUserSignCondition() {
        // 1.获取当前登录用户id
        Long userId = UserContext.getUser();
        // 2.获取日期
        LocalDate now = LocalDate.now();
        // 3.拼接key
        String key = RedisConstants.SIGN_DATE_SUFFIX_FORMATTER + userId + now.format(DateUtils.SIGN_DATE_SUFFIX_FORMATTER);
        // 获取本月经历了多少天
        return signRecordDays(key,now.getDayOfMonth());

    }

    /**
     * 核心：从 Redis Bitmap 中解析出签到记录
     * @param key Redis 中的位图 key
     * @param dayCount 本月已过去的天数（例如今天是 15 号，则为 15）
     * @return 每日签到状态列表，索引 0 = 1号，索引 n-1 = 今天
     */
    private List<Byte> signRecordDays(String key, int dayCount) {
        // 4️⃣ 用 BITFIELD 一次性读取前 dayCount 个 bit（从 bit 0 开始）
        List<Long> result = redisTemplate.opsForValue().bitField(
                key,
                BitFieldSubCommands.create()
                        .get(BitFieldSubCommands.BitFieldType.unsigned(dayCount))
                        .valueAt(0)
        );
        // 5️⃣ 如果 Redis 返回 null 或空，说明从未签到 → 返回全 0
        if (CollUtils.isEmpty(result) || result.get(0) == null){
            return Collections.nCopies(dayCount, (byte) 0);
        }
        // 6️⃣ 取出十进制数值（这个数的二进制每一位代表一天的签到状态）
        int bitmapValue = result.get(0).intValue();
        // 7️⃣ 逐位解析：从 bit 0（1号）开始，依次取最低位
        List<Byte> signStatus = new ArrayList<>(dayCount);
        for (int i = 0; i < dayCount; i++) {
            // num & 1 → 取最低位（0 或 1） 结构如果bitmapValue是1则为1 ，是0则为0
            signStatus.add((byte) (bitmapValue & 1));
            // 无符号右移一位，准备取下一天(把数字右移一位，最后一位被舍弃，倒数第二位成了最后一位)
            bitmapValue >>>= 1;
        }
        return signStatus;
    }

    // 连续签到次数
    private int countSignDays(String key, int len) {
        // 1.获取本月从第一天开始，到今天为止的所有签到记录
        List<Long> result = redisTemplate.opsForValue()
                .bitField(key, BitFieldSubCommands.create().get(
                        BitFieldSubCommands.BitFieldType.unsigned(len)
                        // BitFieldSubCommands.BitFieldType.unsigned(len) 查询的天数
                ).valueAt(0));// 从0开始查询
        if (CollUtils.isEmpty(result)) {
            return 0;
        }
        Long num = result.get(0);
        // 2.定义一个计数器
        int count = 0;
        while ((num & 1) == 1) {
            count++;
            // 5.把数字右移一位，最后一位被舍弃，倒数第二位成了最后一位
            num >>>= 1;
        }
        return count;
    }
}
